﻿using System;
using System.Reflection;
using OverStore.Configuration.Validation;
using OverStore.Runtime.ObjectRepository.Accessors;

namespace OverStore.Configuration.ObjectRepository.Accessors
{
    /// <summary>
    /// Allows to access result of specified property.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TValue">The type of the result.</typeparam>
    public sealed class PropertyAccessor<TInstance, TValue> : ITypedAccessor<TInstance, TValue>, ISupportValidation
        where TInstance : class
    {
        private PropertyInfo _PropertyInfo;

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyAccessor{TInstance, TValue}"/> class.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <exception cref="ArgumentNullException"><paramref name="propertyName"/> is null reference or empty string.</exception>
        public PropertyAccessor(string propertyName)
        {
            if (String.IsNullOrEmpty(propertyName))
                throw new ArgumentNullException("propertyName");
            PropertyName = propertyName;
        }

        private string PropertyName { get; set; }

        private PropertyInfo PropertyInfo
        {
            get
            {
                if (_PropertyInfo == null)
                    _PropertyInfo = typeof(TInstance).GetProperty(PropertyName, BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                return _PropertyInfo;
            }
        }

        /// <summary>
        /// Gets the result from specified instance.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <returns>Value got from instance.</returns>
        public TValue GetValue(TInstance instance)
        {
            return (TValue)PropertyInfo.GetValue(instance, null);
        }

        /// <summary>
        /// Sets the result to specified instance.
        /// </summary>
        /// <param name="instance">The instance to set result to.</param>
        /// <param name="value">The result for set.</param>
        public void SetValue(TInstance instance, TValue value)
        {
            PropertyInfo.SetValue(instance, value, null);
        }

        /// <summary>
        /// Get result from specified instance.
        /// </summary>
        object IAccessor.GetValue(object instance)
        {
            return GetValue((TInstance)instance);
        }

        /// <summary>
        /// Set result to specified instance.
        /// </summary>
        void IAccessor.SetValue(object instance, object value)
        {
            SetValue((TInstance)instance, (TValue)value);
        }

        /// <summary>
        /// Perform component validation. Method should validate only
        /// component configuration itself, not sub components.
        /// If configuration is valid, null result should returns.
        /// </summary>
        public ValidationResult Validate()
        {
            return new ValidationResult(this)
                .AddError(PropertyInfo == null, "Property {0} not found in type {1}", PropertyName, typeof(TInstance))
                .AddError(
                      PropertyInfo != null && !typeof(TValue).IsAssignableFrom(PropertyInfo.PropertyType),
                      "Value type {0} is not assignable from property type {1}.",
                      typeof(TValue),
                      PropertyInfo.PropertyType);
        }
    }
}